using ModelContextProtocol.Server;
using System.ComponentModel;
using System.Text;
using System.Net.Http.Json;
using System.Text.Json.Serialization;
using System.Globalization;
using System.Text.Json;
using Microsoft.Extensions.Caching.Memory;
using System.Net;
using System.Linq;
using MediatR;
using WeatherMcpServer.Application.Alerts.Commands;

namespace WeatherMcpServer
{
    /// <summary>
    /// MCP 天气工具集合。
    /// </summary>
    /// <remarks>
    /// - 通过高德开放平台(AMap) Web 服务获取逆地理编码与天气预报。
    /// - 通过 <see cref="IMediator"/> 分发指令以评估规则并创建告警。
    /// - 通过 <see cref="IMemoryCache"/> 对城市预报做短期缓存以降低外部调用频率。
    /// <para>必须设置环境变量 AMAP_KEY（见 `Properties/launchSettings.json`）。</para>
    /// </remarks>
    [McpServerToolType]
    public class WeatherTool
    {
        private readonly HttpClient _httpClient;
        private readonly string _amapKey;
        private readonly IMemoryCache _cache;
        private readonly IMediator _mediator;

        private const string ApiKey = ""; // 已弃用（WeatherAPI）。现使用 AMAP_KEY。

        /// <summary>
        /// 构造函数。
        /// </summary>
        /// <param name="httpClientFactory">用于创建访问外部 API 的 <see cref="HttpClient"/>。</param>
        /// <param name="cache">用于缓存天气预报文本。</param>
        /// <param name="mediator">用于分发评估天气数据的指令。</param>
        public WeatherTool(IHttpClientFactory httpClientFactory, IMemoryCache cache, IMediator mediator)
        {
            _httpClient = httpClientFactory.CreateClient();
            // 读取高德 Web 服务密钥
            _amapKey = (Environment.GetEnvironmentVariable("AMAP_KEY") ?? string.Empty).Trim();
            _cache = cache;
            _mediator = mediator;
        }

        /// <summary>
        /// 批量查询工具：接收一组坐标(JSON 文本)并并发获取天气预报。
        /// </summary>
        /// <param name="coordinatesJson">坐标数组的 JSON 文本，例如 <c>[{"Lat":39.9,"Lon":116.4},{"Lat":31.2,"Lon":121.5}]</c>。</param>
        /// <returns>与传入顺序一致的天气预报文本数组。</returns>
        [McpServerTool(Name = "batch-query")]
        [Description("批量查询多个坐标的天气预报（内部并发，结果顺序与传入一致）。参数为 JSON 文本，如: [{\"Lat\":39.9,\"Lon\":116.4},{\"Lat\":31.2,\"Lon\":121.5}] ")]
        public async Task<IEnumerable<string>> BatchQueryAsync([Description("坐标数组的 JSON 文本")] string coordinatesJson)
        {
            // 将 JSON 文本反序列化为坐标列表，如解析失败则使用空列表
            List<Coordinate>? coords = null;
            try
            {
                coords = string.IsNullOrWhiteSpace(coordinatesJson)
                    ? new List<Coordinate>()
                    : JsonSerializer.Deserialize<List<Coordinate>>(coordinatesJson);
            }
            catch
            {
                coords = new List<Coordinate>();
            }

            // 并发请求各坐标的天气
            var tasks = (coords ?? new List<Coordinate>()).Select(c => GetForecastAsync(c.Lat, c.Lon));
            var result = await Task.WhenAll(tasks);
            return result;
        }

        /// <summary>
        /// 获取某一经纬度对应行政区的 3 日天气预报（文本）。
        /// </summary>
        /// <param name="latitude">纬度，十进制度。</param>
        /// <param name="longitude">经度，十进制度。</param>
        /// <returns>格式化后的天气预报文本；错误时返回错误提示。</returns>
        [McpServerTool(Name = "get-forecast")]
        [Description("获取指定坐标的天气预报")]
        public async Task<string> GetForecastAsync([Description("纬度")] double latitude, [Description("经度")] double longitude)
        {
            if (string.IsNullOrWhiteSpace(_amapKey))
            {
                return "未配置 AMAP_KEY 环境变量，请在 launchSettings.json 或系统环境变量中设置后重试。";
            }

            // 1) 通过逆地理获取 adcode（注意高德 location 参数顺序：lon,lat），使用 InvariantCulture 避免小数点在本地化下变成逗号
            var lonStr = longitude.ToString(CultureInfo.InvariantCulture);
            var latStr = latitude.ToString(CultureInfo.InvariantCulture);
            var regeoUrl = $"https://restapi.amap.com/v3/geocode/regeo?location={lonStr},{latStr}&key={_amapKey}";

            try
            {
                var regeoResp = await _httpClient.GetAsync(regeoUrl);
                if (!regeoResp.IsSuccessStatusCode)
                {
                    if (regeoResp.StatusCode == HttpStatusCode.TooManyRequests)
                    {
                        var retryAfter = regeoResp.Headers.RetryAfter?.Delta ?? TimeSpan.FromSeconds(10);
                        return $"请求过于频繁（429）。请在 {retryAfter.TotalSeconds:N0}s 后重试（逆地理）。";
                    }
                    var errorBody = await regeoResp.Content.ReadAsStringAsync();
                    return $"无法获取行政区编码：{regeoResp.StatusCode} {errorBody}";
                }

                var regeoData = await regeoResp.Content.ReadFromJsonAsync<AmapRegeoResponse>();
                if (regeoData is null || regeoData.Status != "1")
                {
                    return "逆地理解析失败（Status!=1）";
                }

                var adcode = ExtractAdcode(regeoData.RegeoCode);
                if (string.IsNullOrWhiteSpace(adcode))
                {
                    return "未能获取到有效的 adcode";
                }

                // 2) 获取天气预报（extensions=all 返回预报）
                var cacheKey = $"forecast:{adcode}";
                if (_cache.TryGetValue(cacheKey, out string cached))
                {
                    return cached;
                }

                var weatherUrl = $"https://restapi.amap.com/v3/weather/weatherInfo?city={adcode}&extensions=all&key={_amapKey}";
                var weatherResp = await _httpClient.GetAsync(weatherUrl);
                if (!weatherResp.IsSuccessStatusCode)
                {
                    if (weatherResp.StatusCode == HttpStatusCode.TooManyRequests)
                    {
                        var retryAfter = weatherResp.Headers.RetryAfter?.Delta ?? TimeSpan.FromSeconds(10);
                        return $"请求过于频繁（429）。请在 {retryAfter.TotalSeconds:N0}s 后重试（天气接口）。";
                    }
                    var errorBody = await weatherResp.Content.ReadAsStringAsync();
                    return $"无法获取天气数据：{weatherResp.StatusCode} {errorBody}";
                }

                var weatherData = await weatherResp.Content.ReadFromJsonAsync<AmapForecastResponse>();
                if (weatherData is null || weatherData.Status != "1" || weatherData.Forecasts is null || weatherData.Forecasts.Length == 0)
                {
                    return "天气数据解析失败（Status!=1 或数据为空）";
                }

                var formatted = FormatAmapForecast(weatherData);
                _cache.Set(cacheKey, formatted, TimeSpan.FromMinutes(10));
                return formatted;
            }
            catch
            {
                return "无法获取天气数据";
            }
        }

        /// <summary>
        /// 根据经纬度获取预报并按规则触发预警，返回创建的预警ID列表摘要。
        /// </summary>
        /// <param name="latitude">纬度，十进制度。</param>
        /// <param name="longitude">经度，十进制度。</param>
        /// <param name="dayOffset">选择第几天（0-2）。</param>
        /// <returns>包含区域、所选预报摘要与创建的告警 ID 列表的文本。</returns>
        [McpServerTool(Name = "evaluate-forecast")]
        [Description("根据经纬度获取未来3天预报，抽取指标并按规则触发预警。可通过 dayOffset 选择第几天（0=今天/第1条，最大2）。返回创建的预警ID列表摘要。")]
        public async Task<string> EvaluateForecastAsync(
            [Description("纬度")] double latitude,
            [Description("经度")] double longitude,
            [Description("选择第几天（0-2）")] int dayOffset = 0)
        {
            if (string.IsNullOrWhiteSpace(_amapKey))
            {
                return "未配置 AMAP_KEY 环境变量，请在 launchSettings.json 或系统环境变量中设置后重试。";
            }

            if (dayOffset < 0) dayOffset = 0;
            if (dayOffset > 2) dayOffset = 2;

            try
            {
                var result = await EvaluateForecastIdsAsync(latitude, longitude, dayOffset);
                if (result is null)
                {
                    return "评估失败：外部接口异常或解析错误";
                }
                var sb = new StringBuilder();
                sb.AppendLine($"区域：{result.RegionName}（adcode={result.RegionCode}），报告时间：{result.ReportTime}");
                sb.AppendLine($"选择预报日：{result.CastDate} | 白天{result.DayWeather} {result.DayTemp}°C | 夜间{result.NightWeather} {result.NightTemp}°C | 风力(日/夜)：{result.DayPower}/{result.NightPower}");
                sb.AppendLine($"触发预警条数：{result.AlertIds.Count}，ID：{string.Join(",", result.AlertIds)}");
                return sb.ToString();
            }
            catch
            {
                return "评估失败：外部接口异常或解析错误";
            }
        }

        /// <summary>
        /// 面向 API 的评估方法：返回触发的预警ID及关键上下文，便于控制器直接调用。
        /// </summary>
        public async Task<EvaluateResult?> EvaluateForecastIdsAsync(double latitude, double longitude, int dayOffset = 0)
        {
            var adcode = await GetAdcodeAsync(longitude, latitude);
            if (string.IsNullOrWhiteSpace(adcode)) return null;

            var weatherUrl = $"https://restapi.amap.com/v3/weather/weatherInfo?city={adcode}&extensions=all&key={_amapKey}";
            var weatherResp = await _httpClient.GetAsync(weatherUrl);
            if (!weatherResp.IsSuccessStatusCode) return null;
            var weatherData = await weatherResp.Content.ReadFromJsonAsync<AmapForecastResponse>();
            if (weatherData is null || weatherData.Status != "1" || weatherData.Forecasts is null || weatherData.Forecasts.Length == 0) return null;

            var f = weatherData.Forecasts[0];
            if (f.Casts is null || f.Casts.Length == 0) return null;
            if (dayOffset < 0) dayOffset = 0; if (dayOffset > 2) dayOffset = 2;
            var idx = Math.Min(dayOffset, Math.Max(0, f.Casts.Length - 1));
            var cast = f.Casts[idx];

            double? dayTemp = TryParseDouble(cast.DayTemp);
            double? nightTemp = TryParseDouble(cast.NightTemp);
            double? tempMax = MaxNullable(dayTemp, nightTemp);
            double? tempMin = MinNullable(dayTemp, nightTemp);

            // 新增：从天气现象文本推断降雨量级与天气类型，并解析风力
            var inferred = InferIndicatorsFromCast(cast);
            double? rainMm = inferred.RainMm ?? null; // 若无法推断，保持 null
            double? windLevel = inferred.WindLevel;
            var inferredTypes = inferred.Types; // 可能包含多个类型（如雨+风）

            var allIds = new List<long>();
            if (inferredTypes == null || inferredTypes.Count == 0)
            {
                // 无法推断类型时，走一次通用评估（不指定 TypeHint）
                var ids = await _mediator.Send(new EvaluateWeatherDataCommand(
                    RegionCode: f.Adcode,
                    RegionName: f.City,
                    TemperatureMax: tempMax,
                    TemperatureMin: tempMin,
                    RainMm: rainMm,
                    WindLevel: windLevel,
                    TypeHint: null
                ));
                allIds.AddRange(ids);
            }
            else
            {
                // 针对推断出的每种类型单独评估，便于按类型过滤匹配规则
                foreach (var t in inferredTypes.Distinct())
                {
                    var ids = await _mediator.Send(new EvaluateWeatherDataCommand(
                        RegionCode: f.Adcode,
                        RegionName: f.City,
                        TemperatureMax: tempMax,
                        TemperatureMin: tempMin,
                        RainMm: rainMm,
                        WindLevel: windLevel,
                        TypeHint: t
                    ));
                    if (ids is { Count: > 0 }) allIds.AddRange(ids);
                }
            }

            return new EvaluateResult(
                f.Adcode,
                f.City,
                f.ReportTime,
                cast.Date,
                cast.DayWeather,
                cast.NightWeather,
                cast.DayTemp,
                cast.NightTemp,
                cast.DayPower,
                cast.NightPower,
                allIds
            );
        }

        /// <summary>
        /// 获取经纬度对应的行政区编码 adcode。使用内存缓存降低后续调用延迟。
        /// </summary>
        private async Task<string?> GetAdcodeAsync(double longitude, double latitude)
        {
            // 归一化经纬度以提高缓存命中率（保留 4 位小数 ≈ 11m）
            var lonStr = Math.Round(longitude, 4).ToString(CultureInfo.InvariantCulture);
            var latStr = Math.Round(latitude, 4).ToString(CultureInfo.InvariantCulture);
            var cacheKey = $"regeo:{lonStr},{latStr}";

            if (_cache.TryGetValue(cacheKey, out string cachedCode))
            {
                return cachedCode;
            }

            var url = $"https://restapi.amap.com/v3/geocode/regeo?location={lonStr},{latStr}&key={_amapKey}";
            var regeoResp = await _httpClient.GetAsync(url);
            if (!regeoResp.IsSuccessStatusCode)
            {
                return null;
            }

            var regeoData = await regeoResp.Content.ReadFromJsonAsync<AmapRegeoResponse>();
            if (regeoData is null || regeoData.Status != "1")
            {
                return null;
            }

            var code = ExtractAdcode(regeoData.RegeoCode);
            if (!string.IsNullOrWhiteSpace(code))
            {
                _cache.Set(cacheKey, code, TimeSpan.FromHours(12));
            }
            return code;
        }

        /// <summary>
        /// 将 AMap 天气预报数据格式化为文本。
        /// </summary>
        private static string FormatAmapForecast(AmapForecastResponse data)
        {
            var sb = new StringBuilder();
            var f = data.Forecasts![0];
            sb.AppendLine($"城市：{f.City}（{f.Province}）");
            sb.AppendLine($"报告时间：{f.ReportTime}");
            sb.AppendLine("未来三天预报：");
            foreach (var (cast, idx) in f.Casts.Take(3).Select((c, i) => (c, i)))
            {
                sb.AppendLine($"{cast.Date} | 白天 {cast.DayWeather} {cast.DayTemp}°C | 夜间 {cast.NightWeather} {cast.NightTemp}°C");
            }
            return sb.ToString();
        }

        /// <summary>
        /// 将字符串解析为 double，尽量兼容多种文化与掺杂字符的情况。
        /// </summary>
        private static double? TryParseDouble(string? s)
        {
            if (string.IsNullOrWhiteSpace(s)) return null;
            if (double.TryParse(s, NumberStyles.Any, CultureInfo.InvariantCulture, out var v)) return v;
            if (double.TryParse(s, NumberStyles.Any, CultureInfo.CurrentCulture, out v)) return v;
            var filtered = new string(s.Where(ch => char.IsDigit(ch) || ch == '.' || ch == '-' || ch == '+').ToArray());
            if (double.TryParse(filtered, NumberStyles.Any, CultureInfo.InvariantCulture, out v)) return v;
            return null;
        }

        /// <summary>
        /// 可空数值的最大值。
        /// </summary>
        private static double? MaxNullable(double? a, double? b)
            => (a, b) switch { (null, null) => null, (double x, null) => x, (null, double y) => y, (double x, double y) => Math.Max(x, y) };

        /// <summary>
        /// 可空数值的最小值。
        /// </summary>
        private static double? MinNullable(double? a, double? b)
            => (a, b) switch { (null, null) => null, (double x, null) => x, (null, double y) => y, (double x, double y) => Math.Min(x, y) };

        /// <summary>
        /// 解析风力等级文本（如“3-4”“≤3”“7”），返回可能的最大数值。
        /// </summary>
        private static double? ParseWindLevel(string? day, string? night)
        {
            double? pd = ExtractMaxNumber(day);
            double? pn = ExtractMaxNumber(night);
            return MaxNullable(pd, pn);
        }

        /// <summary>
        /// 从字符串中提取所有数字片段，返回其中最大值。
        /// </summary>
        private static double? ExtractMaxNumber(string? s)
        {
            if (string.IsNullOrWhiteSpace(s)) return null;
            var nums = new List<double>();
            var buf = new StringBuilder();
            void Flush()
            {
                if (buf.Length == 0) return;
                if (double.TryParse(buf.ToString(), NumberStyles.Any, CultureInfo.InvariantCulture, out var v)) nums.Add(v);
                buf.Clear();
            }
            foreach (var ch in s)
            {
                if (char.IsDigit(ch) || ch == '.' || ch == '-') buf.Append(ch); else Flush();
            }
            Flush();
            return nums.Count == 0 ? null : nums.Max();
        }

        // 新增：根据高德预报的天气现象文本推断指标（降雨量级/风力）与类型
        private static (List<Data.Entities.WeatherType> Types, double? RainMm, double? WindLevel) InferIndicatorsFromCast(Cast cast)
        {
            var types = new List<Data.Entities.WeatherType>();
            double? rain = null;

            // 统一小写/去空白
            var dayW = (cast.DayWeather ?? string.Empty).Trim();
            var nightW = (cast.NightWeather ?? string.Empty).Trim();
            var joined = string.Join(";", new[] { dayW, nightW });

            // 雨量（经验映射）：
            // 小雨≈2mm，中雨≈10mm，大雨≈25mm，暴雨≈50mm，大暴雨≈100mm，特大暴雨≈250mm
            double? MapRain(string s)
            {
                if (string.IsNullOrEmpty(s)) return null;
                if (s.Contains("特大暴雨")) return 250;
                if (s.Contains("大暴雨")) return 100;
                if (s.Contains("暴雨")) return 50;
                if (s.Contains("大雨")) return 25;
                if (s.Contains("中雨")) return 10;
                if (s.Contains("小雨")) return 2;
                if (s.Contains("阵雨") || s.Contains("雷阵雨")) return 5;
                return null;
            }

            double? r1 = MapRain(dayW);
            double? r2 = MapRain(nightW);
            rain = MaxNullable(r1, r2);
            if (rain.HasValue && rain.Value >= 25) // 大雨及以上，当作 HeavyRain
            {
                types.Add(Data.Entities.WeatherType.HeavyRain);
            }

            // 雪
            if (joined.Contains("雪"))
            {
                types.Add(Data.Entities.WeatherType.Snow);
            }

            // 雾
            if (joined.Contains("雾"))
            {
                types.Add(Data.Entities.WeatherType.Fog);
            }

            // 强对流/风
            if (joined.Contains("雷") || joined.Contains("雷暴") || joined.Contains("大风"))
            {
                types.Add(Data.Entities.WeatherType.Storm);
            }

            // 风力解析沿用 ParseWindLevel
            var wind = ParseWindLevel(cast.DayPower, cast.NightPower);
            // 强风阈值：≥6 级（可由规则进一步精细化）
            if (wind.HasValue && wind.Value >= 6)
            {
                types.Add(Data.Entities.WeatherType.Wind);
            }

            return (types, rain, wind);
        }

        // ====== AMap Regeo Models ======
        public record AmapRegeoResponse(
            [property: JsonPropertyName("status")] string Status,
            [property: JsonPropertyName("regeocode")] JsonElement? RegeoCode);

        private static string? ExtractAdcode(JsonElement? regeo)
        {
            if (regeo is null)
            {
                return null;
            }
            var elem = regeo.Value;
            // addressComponent 可能是对象或数组（兼容批量或不同返回）
            if (elem.ValueKind == JsonValueKind.Object)
            {
                if (elem.TryGetProperty("addressComponent", out var address))
                {
                    return ExtractAdcodeFromAddress(address);
                }
            }
            else if (elem.ValueKind == JsonValueKind.Array)
            {
                foreach (var item in elem.EnumerateArray())
                {
                    var code = ExtractAdcode(new JsonElement?(item));
                    if (!string.IsNullOrWhiteSpace(code)) return code;
                }
            }
            return null;
        }

        private static string? ExtractAdcodeFromAddress(JsonElement address)
        {
            if (address.ValueKind == JsonValueKind.Object)
            {
                if (address.TryGetProperty("adcode", out var ad))
                {
                    if (ad.ValueKind == JsonValueKind.String) return ad.GetString();
                    if (ad.ValueKind == JsonValueKind.Array)
                    {
                        foreach (var a in ad.EnumerateArray())
                        {
                            if (a.ValueKind == JsonValueKind.String) return a.GetString();
                        }
                    }
                }
            }
            else if (address.ValueKind == JsonValueKind.Array)
            {
                foreach (var item in address.EnumerateArray())
                {
                    var code = ExtractAdcodeFromAddress(item);
                    if (!string.IsNullOrWhiteSpace(code)) return code;
                }
            }
            return null;
        }

        // ====== AMap Forecast Models ======
        public record AmapForecastResponse(
            [property: JsonPropertyName("status")] string Status,
            [property: JsonPropertyName("forecasts")] ForecastItem[]? Forecasts);

        public record ForecastItem(
            [property: JsonPropertyName("city")] string City,
            [property: JsonPropertyName("adcode")] string Adcode,
            [property: JsonPropertyName("province")] string Province,
            [property: JsonPropertyName("reporttime")] string ReportTime,
            [property: JsonPropertyName("casts")] Cast[] Casts);

        public record Cast(
            [property: JsonPropertyName("date")] string Date,
            [property: JsonPropertyName("week")] string Week,
            [property: JsonPropertyName("dayweather")] string DayWeather,
            [property: JsonPropertyName("nightweather")] string NightWeather,
            [property: JsonPropertyName("daytemp")] string DayTemp,
            [property: JsonPropertyName("nighttemp")] string NightTemp,
            [property: JsonPropertyName("daywind")] string DayWind,
            [property: JsonPropertyName("nightwind")] string NightWind,
            [property: JsonPropertyName("daypower")] string DayPower,
            [property: JsonPropertyName("nightpower")] string NightPower);

        // ====== Records ======
        /// <summary>
        /// 简单坐标记录，<see cref="BatchQueryAsync(string)"/> 反序列化的目标类型。
        /// </summary>
        public record Coordinate(double Lat, double Lon);

        /// <summary>
        /// 评估结果（提供给 Controller 使用）。
        /// </summary>
        public record EvaluateResult(
            string RegionCode,
            string RegionName,
            string ReportTime,
            string CastDate,
            string DayWeather,
            string NightWeather,
            string DayTemp,
            string NightTemp,
            string DayPower,
            string NightPower,
            IReadOnlyList<long> AlertIds
        );

        /// <summary>
        /// 通过地名查询 3 日天气预报（文本）。优先使用高德地理编码获取 adcode 后查询。
        /// </summary>
        public async Task<string> GetForecastByAddressAsync(string address)
        {
            if (string.IsNullOrWhiteSpace(_amapKey))
            {
                return "未配置 AMAP_KEY 环境变量，请在 launchSettings.json 或系统环境变量中设置后重试。";
            }
            var addr = (address ?? string.Empty).Trim();
            if (addr.Length == 0) return "地址不能为空";

            try
            {
                var geo = await GeocodeAddressAsync(addr);
                if (geo is null) return "未能根据地址解析到位置";
                var g = geo.Value;

                // 若有 adcode 直接走城市缓存
                if (!string.IsNullOrWhiteSpace(g.Adcode))
                {
                    var cacheKey = $"forecast:{g.Adcode}";
                    if (_cache.TryGetValue(cacheKey, out string cached)) return cached;

                    var weatherUrl = $"https://restapi.amap.com/v3/weather/weatherInfo?city={g.Adcode}&extensions=all&key={_amapKey}";
                    var weatherResp = await _httpClient.GetAsync(weatherUrl);
                    if (!weatherResp.IsSuccessStatusCode)
                    {
                        if (weatherResp.StatusCode == HttpStatusCode.TooManyRequests)
                        {
                            var retryAfter = weatherResp.Headers.RetryAfter?.Delta ?? TimeSpan.FromSeconds(10);
                            return $"请求过于频繁（429）。请在 {retryAfter.TotalSeconds:N0}s 后重试（天气接口）。";
                        }
                        var errorBody = await weatherResp.Content.ReadAsStringAsync();
                        return $"无法获取天气数据：{weatherResp.StatusCode} {errorBody}";
                    }

                    var weatherData = await weatherResp.Content.ReadFromJsonAsync<AmapForecastResponse>();
                    if (weatherData is null || weatherData.Status != "1" || weatherData.Forecasts is null || weatherData.Forecasts.Length == 0)
                        return "天气数据解析失败（Status!=1 或数据为空）";

                    var formatted = FormatAmapForecast(weatherData);
                    _cache.Set(cacheKey, formatted, TimeSpan.FromMinutes(10));
                    return formatted;
                }

                // 否则回退：用坐标走现有逻辑
                if (g.Latitude is double la && g.Longitude is double lo)
                {
                    return await GetForecastAsync(la, lo);
                }
                return "未能根据地址解析到有效坐标";
            }
            catch
            {
                return "无法获取天气数据";
            }
        }

        /// <summary>
        /// 通过地名查询并评估预报，返回触发的预警ID及上下文。
        /// </summary>
        public async Task<EvaluateResult?> EvaluateByAddressAsync(string address, int dayOffset = 0)
        {
            if (string.IsNullOrWhiteSpace(_amapKey)) return null;
            var addr = (address ?? string.Empty).Trim();
            if (addr.Length == 0) return null;

            var geo = await GeocodeAddressAsync(addr);
            if (geo is null) return null;
            var g2 = geo.Value;

            if (!string.IsNullOrWhiteSpace(g2.Adcode))
            {
                // 直接用 adcode 拉取 4 天预报，再选 dayOffset 评估
                var url = $"https://restapi.amap.com/v3/weather/weatherInfo?city={g2.Adcode}&extensions=all&key={_amapKey}";
                var resp = await _httpClient.GetAsync(url);
                if (!resp.IsSuccessStatusCode) return null;
                var data = await resp.Content.ReadFromJsonAsync<AmapForecastResponse>();
                if (data is null || data.Status != "1" || data.Forecasts is null || data.Forecasts.Length == 0) return null;

                var f = data.Forecasts[0];
                if (f.Casts is null || f.Casts.Length == 0) return null;
                if (dayOffset < 0) dayOffset = 0; if (dayOffset > 2) dayOffset = 2;
                var idx = Math.Min(dayOffset, Math.Max(0, f.Casts.Length - 1));
                var cast = f.Casts[idx];

                double? dayTemp = TryParseDouble(cast.DayTemp);
                double? nightTemp = TryParseDouble(cast.NightTemp);
                double? tempMax = MaxNullable(dayTemp, nightTemp);
                double? tempMin = MinNullable(dayTemp, nightTemp);

                var inferred = InferIndicatorsFromCast(cast);
                double? rainMm = inferred.RainMm;
                double? windLevel = inferred.WindLevel;
                var inferredTypes = inferred.Types;

                var allIds = new List<long>();
                if (inferredTypes == null || inferredTypes.Count == 0)
                {
                    var ids = await _mediator.Send(new EvaluateWeatherDataCommand(
                        RegionCode: f.Adcode,
                        RegionName: f.City,
                        TemperatureMax: tempMax,
                        TemperatureMin: tempMin,
                        RainMm: rainMm,
                        WindLevel: windLevel,
                        TypeHint: null
                    ));
                    allIds.AddRange(ids);
                }
                else
                {
                    foreach (var t in inferredTypes.Distinct())
                    {
                        var ids = await _mediator.Send(new EvaluateWeatherDataCommand(
                            RegionCode: f.Adcode,
                            RegionName: f.City,
                            TemperatureMax: tempMax,
                            TemperatureMin: tempMin,
                            RainMm: rainMm,
                            WindLevel: windLevel,
                            TypeHint: t
                        ));
                        if (ids is { Count: > 0 }) allIds.AddRange(ids);
                    }
                }

                return new EvaluateResult(
                    f.Adcode,
                    f.City,
                    f.ReportTime,
                    cast.Date,
                    cast.DayWeather,
                    cast.NightWeather,
                    cast.DayTemp,
                    cast.NightTemp,
                    cast.DayPower,
                    cast.NightPower,
                    allIds
                );
            }

            // 无 adcode 时回退为经纬度评估
            if (g2.Latitude is double la && g2.Longitude is double lo)
            {
                return await EvaluateForecastIdsAsync(la, lo, dayOffset);
            }
            return null;
        }

        /// <summary>
        /// 使用高德地理编码将地址解析为 adcode 与坐标。
        /// </summary>
        private async Task<(string? Adcode, double? Latitude, double? Longitude, string? Name)?> GeocodeAddressAsync(string address)
        {
            var addr = (address ?? string.Empty).Trim();
            if (addr.Length == 0) return null;

            var cacheKey = $"geocode:{addr}";
            if (_cache.TryGetValue(cacheKey, out (string? Adcode, double? Latitude, double? Longitude, string? Name) cached))
            {
                return cached;
            }

            var url = $"https://restapi.amap.com/v3/geocode/geo?address={Uri.EscapeDataString(addr)}&key={_amapKey}";
            var resp = await _httpClient.GetAsync(url);
            if (!resp.IsSuccessStatusCode) return null;

            var json = await resp.Content.ReadFromJsonAsync<JsonElement>();
            if (json.ValueKind != JsonValueKind.Object) return null;
            if (!json.TryGetProperty("status", out var statusProp) || statusProp.GetString() != "1") return null;
            if (!json.TryGetProperty("geocodes", out var arr) || arr.ValueKind != JsonValueKind.Array || arr.GetArrayLength() == 0) return null;
            var first = arr[0];
            string? adcode = first.TryGetProperty("adcode", out var adProp) ? adProp.GetString() : null;
            string? location = first.TryGetProperty("location", out var locProp) ? locProp.GetString() : null;
            string? name = first.TryGetProperty("formatted_address", out var nameProp) ? nameProp.GetString() : null;

            double? lat = null, lon = null;
            if (!string.IsNullOrWhiteSpace(location) && location!.Contains(','))
            {
                var parts = location.Split(',');
                if (parts.Length >= 2)
                {
                    if (double.TryParse(parts[1], NumberStyles.Any, CultureInfo.InvariantCulture, out var la)) lat = la;
                    if (double.TryParse(parts[0], NumberStyles.Any, CultureInfo.InvariantCulture, out var lo)) lon = lo;
                }
            }

            var result = (adcode, lat, lon, name);
            _cache.Set(cacheKey, result, TimeSpan.FromHours(12));
            return result;
        }
    }
}
